package org.ywf.framework.sftp;

import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.function.Predicate;

/**
 * TODO
 *
 * @Author YWF
 * @Date 2023/2/9 15:18
 */
@Slf4j
@Service
public class SftpFileService {

    private SftpProperties config;

    @Autowired
    public SftpFileService(SftpProperties config) {
        this.config = config;
    }

    // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
    private static final String SESSION_CONFIG_STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking";
    // 设置第一次登陆的时候提示，可选值：(ask | yes | no)
    private static final String SESSION_CONFIG_GSSAPIAuthentication  = "userauth.gssapi-with-mic";

    /**
     * 创建SFTP连接
     *
     * @return
     * @throws Exception
     */
    private ChannelSftp createSftp() throws Exception {
        JSch jsch = new JSch();
        log.info("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use password[" + config.getPassword() + "]");

        Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
        session.setPassword(config.getPassword());
        session.connect(config.getSessionConnectTimeout());

        log.info("Session connected to {}.", config.getHost());

        Channel channel = session.openChannel(config.getProtocol());
        channel.connect(config.getChannelConnectedTimeout());

        log.info("Channel created to {}.", config.getHost());

        return (ChannelSftp) channel;
    }

    /**
     * 加密秘钥方式登陆
     *
     * @return
     */
    private ChannelSftp connectByKey() throws Exception {
        JSch jsch = new JSch();

        // 设置密钥和密码 ,支持密钥的方式登陆
        if (StrUtil.isNotBlank(config.getPrivateKey())) {
            if (StrUtil.isNotBlank(config.getPassphrase())) {
                // 设置带口令的密钥
                jsch.addIdentity(config.getPrivateKey(), config.getPassphrase());
            } else {
                // 设置不带口令的密钥
                jsch.addIdentity(config.getPrivateKey());
            }
        }
        log.info("Try to connect sftp[" + config.getUsername() + "@" + config.getHost() + "], use private key[" + config.getPrivateKey()
                + "] with passphrase[" + config.getPassphrase() + "]");

        Session session = createSession(jsch, config.getHost(), config.getUsername(), config.getPort());
        // 设置登陆超时时间
        session.connect(config.getSessionConnectTimeout());
        log.info("Session connected to " + config.getHost() + ".");

        // 创建sftp通信通道
        Channel channel = session.openChannel(config.getProtocol());
        channel.connect(config.getChannelConnectedTimeout());
        log.info("Channel created to " + config.getHost() + ".");
        return (ChannelSftp) channel;
    }

    /**
     * 创建session
     *
     * @param jsch
     * @param host
     * @param username
     * @param port
     * @return
     * @throws Exception
     */
    private Session createSession(JSch jsch, String host, String username, Integer port) throws Exception {
        Session session = null;

        if (port <= 0) {
            session = jsch.getSession(username, host);
        } else {
            session = jsch.getSession(username, host, port);
        }

        if (session == null) {
            throw new Exception(host + " session is null");
        }

        session.setConfig(SESSION_CONFIG_STRICT_HOST_KEY_CHECKING, config.getSessionStrictHostKeyChecking());
        session.setConfig(SESSION_CONFIG_GSSAPIAuthentication, config.getUserauthGssapi());
        return session;
    }

    /**
     * 关闭连接
     *
     * @param sftp
     */
    private void disconnect(ChannelSftp sftp) {
        try {
            if (sftp != null) {
                if (sftp.isConnected()) {
                    sftp.disconnect();
                } else if (sftp.isClosed()) {
                    log.info("sftp is closed already");
                }
                if (null != sftp.getSession()) {
                    sftp.getSession().disconnect();
                }
            }
        } catch (JSchException e) {
            e.printStackTrace();
        }
    }


    
    public boolean uploadFile(String workpath, String targetPath, InputStream inputStream) throws Exception {
        String privateKey = config.getPrivateKey();
        ChannelSftp sftp = null;
        if (!StrUtil.isBlank(privateKey)) {
            sftp = this.connectByKey();
        } else {
            sftp = this.createSftp();
        }

        try {
            log.info("connect success");
            sftp.cd(workpath);
            log.info("uploadPath: {}", workpath);

            int index = targetPath.lastIndexOf( File.separator );
            log.info("路径分隔符：{}，最后出现的位置：{}",File.separator,index);
            //去掉字符串的前index + 1个字符
            String fileName = targetPath.substring(index + 1);
            log.info("文件名为：{}",fileName);
            sftp.put(inputStream, fileName);
            return true;
        } catch (Exception e) {
            log.error("Upload file failure. TargetPath: {}", targetPath, e);
            throw new Exception("Upload File failure");
        } finally {
            log.info("connect close");
            this.disconnect(sftp);
        }
    }

    
    public boolean uploadFile(String workpath, String targetPath, File file) throws Exception {
        return this.uploadFile(workpath, targetPath, new FileInputStream(file));
    }

    
    public File downloadFile(String workpath, String targetPath) throws Exception {
        log.info("workpath:{}--targetPath:{}",workpath,targetPath);
        String privateKey = config.getPrivateKey();
        ChannelSftp sftp = null;
        if (!StrUtil.isBlank(privateKey)) {
            sftp = this.connectByKey();
        } else {
            sftp = this.createSftp();
        }

        OutputStream outputStream = null;
        try {
            boolean exist = checkExist(sftp, targetPath);
            if (exist) {
                sftp.cd(workpath);
                String fileName=targetPath.substring(targetPath.lastIndexOf("/") + 1);
                String localDownloadPath=config.getLocalDownloadPath();
                //如果配置了本地下载路径 下载到指定目录
                if(StrUtil.isNotBlank(localDownloadPath)){
                    log.info("本地下载路径："+localDownloadPath);
                    if(localDownloadPath.endsWith("/")||localDownloadPath.endsWith("\\")){
                        fileName=localDownloadPath+fileName;
                    }else{
                        if(localDownloadPath.contains("\\")){
                            fileName=localDownloadPath+"\\"+fileName;
                        }else{
                            fileName=localDownloadPath+"/"+fileName;
                        }
                    }
                    log.info("本地文件路径：："+fileName);
                }
                File file = new File(fileName);
                outputStream = new FileOutputStream(file);
                sftp.get(targetPath, outputStream);
                log.info("Download file success. TargetPath: {}", targetPath);
                return file;
            }
            return null;
        } catch (Exception e) {
            log.error("Download file failure. TargetPath: {}", targetPath, e);
            return null;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            this.disconnect(sftp);
        }

    }

    
    public boolean deleteFile(String workpath, String targetPath) throws Exception {
        String privateKey = config.getPrivateKey();
        ChannelSftp sftp = null;
        if (!StrUtil.isBlank(privateKey)) {
            sftp = this.connectByKey();
        } else {
            sftp = this.createSftp();
        }
        try {
            boolean exist = checkExist(sftp, targetPath);
            if (exist) {
                sftp.cd(workpath);
                sftp.rm(targetPath);
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("Delete file failure. TargetPath: {}", targetPath, e);
            return false;
        } finally {
            this.disconnect(sftp);
        }
    }

    /**
     * 文件下载
     * @date 2023/2/11 13:06
     *
     * @param targetPath
     * @param predicate
     */
    public List<File> downloadFiles(String targetPath,  Predicate<String> predicate) throws Exception {
        String privateKey = config.getPrivateKey();
        ChannelSftp sftp = null;
        if (!StrUtil.isBlank(privateKey)) {
            sftp = this.connectByKey();
        } else {
            sftp = this.createSftp();
        }
        Vector vector = sftp.ls(targetPath);
        List<File> fileList = new ArrayList<>();
        OutputStream outputStream = null;
        File file = null;
        try {
            for (int i = 0; i < vector.size(); i++) {
                ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) vector.get(i);
                String filename = lsEntry.getFilename();
                if (predicate.test(filename)) {

                    String fileName=filename.substring(filename.lastIndexOf("/") + 1);
                    String localDownloadPath=config.getLocalDownloadPath();
                    //如果配置了本地下载路径 下载到指定目录
                    if(StrUtil.isNotBlank(localDownloadPath)){
                        log.info("本地下载路径："+localDownloadPath);
                        if(localDownloadPath.endsWith("/")||localDownloadPath.endsWith("\\")){
                            fileName=localDownloadPath+fileName;
                        }else{
                            if(localDownloadPath.contains("\\")){
                                fileName=localDownloadPath+"\\"+fileName;
                            }else{
                                fileName=localDownloadPath+"/"+fileName;
                            }
                        }
                        log.info("本地文件路径："+fileName);
                    }
                    file = new File(fileName);
                    outputStream = new FileOutputStream(file);
                    sftp.get(targetPath + "/" + filename, outputStream);
                    fileList.add(file);
                }
            }
        } catch (Exception e) {
            log.error("download file failure. TargetPath: {}", targetPath, e);
            return null;
        } finally {
            if (outputStream != null) {
                outputStream.close();
            }
            this.disconnect(sftp);
        }
        return fileList;
    }

    /**
     * 判断服务器上是否存在该文件
     *
     * @param targetPath
     * @return
     */
    private boolean checkExist(ChannelSftp sftp, String targetPath) throws SftpException {
        String filename = targetPath.substring(targetPath.lastIndexOf("/") + 1);
        String workpath = targetPath.substring(0, targetPath.lastIndexOf("/"));
        log.info("<<<<<<<<<<<<<<<<<<<<<-----checkExist的文件路径为：{}",workpath);
        sftp.cd(workpath);
        String path = sftp.ls(workpath).toString();
        if (StrUtil.isNotBlank(path) && !path.contains(filename)) {
            log.info(targetPath + " File doesn't exist.");
            return false;
        } else {
            log.info(targetPath + " File already exist.");
            return true;
        }
    }
}
